#include "vm.h"
#include "defs.h"
#include <string.h>
#include "printk.h"
#include "mm.h"
unsigned long  early_pgtbl[512] __attribute__((__aligned__(0x1000)));
//early_pgtbl表示页表。由于setup_vm中的页表只有1层，early_pgtbl既是顶层页表，又是低层页表
unsigned long  early_pgtbl[512] __attribute__((__aligned__(0x1000)));

void setup_vm(void) {
    memset(early_pgtbl, 0x0, PGSIZE);
	//页表只有1层,所以只需要取出vpn2和ppn2
    unsigned long vpn2_1=(PHY_START >> 30) & 0x1ff;
    unsigned long vpn2_2=(VM_START >> 30) & 0x1ff;
    unsigned long ppn2=(PHY_START >> 30) & 0x3ffffff;
	//为对应的页表项赋值。进行了两次映射，需要为两个页表项赋值
  	//将权限 V | R | W | X 位设置为 1，将结果或 15
    early_pgtbl[vpn2_1]=15 | (ppn2<<28); 
    early_pgtbl[vpn2_2]=15 | (ppn2<<28);
    printk("...setup_vm done!\n");
}

void create_mapping(uint64 *pgtbl, uint64 va, uint64 pa, uint64 sz, int perm) {
    /*
    pgtbl 为根页表的基地址
    va, pa 为需要映射的虚拟地址、物理地址
    sz 为映射的大小
    perm 为映射的读写权限

    创建多级页表的时候可以使用 kalloc() 来获取一页作为页表目录
    使用 V bit 来判断页表项是否存在
    */
    uint64 _va=va,_pa=pa;        
    uint64 vpn;
    uint64 pte;
    unsigned long *tbl;
    unsigned long newpage;
	//每次映射PGSIZE的空间。不断映射直至sz全部被映射
    while(_va<=va+sz) {
        tbl=pgtbl; //切换至3级页表
        vpn=(_va >> 30) & 0x1ff;
        pte=tbl[vpn]; //根据vpn2取出3级页表对应的页表项
        if (!pte&1) { //如果页表项为空，新建页
            newpage = (unsigned long)(kalloc() - PA2VA_OFFSET);
            pte = 1 | ((newpage >> 12) << 10);
            tbl[vpn] = pte; 
        }
        //tbl = (unsigned long*)(((pte >> 10) << 12) + PA2VA_OFFSET);
        //切换至下一级页表
        tbl = (unsigned long*)(PA2VA_OFFSET + ((pte >> 10) << 12)); 

        vpn=(_va >> 21) & 0x1ff;
        pte=tbl[vpn]; //根据vpn1取出2级页表对应的页表项
        
        if (!pte&1) { //如果页表项为空，新建页
        
            newpage = (unsigned long)(kalloc() - PA2VA_OFFSET);
            
            pte = 1 | ((newpage >> 12) << 10);
            tbl[vpn] = pte; 
        }
        //tbl = (unsigned long*)(((pte >> 10) << 12) + PA2VA_OFFSET);
        tbl = (unsigned long*)(PA2VA_OFFSET + ((pte >> 10) << 12));  //切换至下一级页表

      	//根据vpn0取出低层页表对应的页表项，直接用地址和权限值对其赋值
        vpn=(_va >> 12) & 0x1ff;
        tbl[vpn]= perm | ((_pa >> 12) << 10) | 1;

        _va+=PGSIZE;
        _pa+=PGSIZE;
    }
}

unsigned long  swapper_pg_dir[512] __attribute__((__aligned__(0x1000)));

extern char* _stext;
extern char* _srodata;
extern char* _sdata;

void setup_vm_final(void) {
    memset(swapper_pg_dir, 0x0, PGSIZE);
    uint64 size;
    // No OpenSBI mapping required
	//映射从PHY_START+OPENSBI_SIZE开始，跳过OpenSBI
    uint64 va=VM_START+OPENSBI_SIZE, pa=PHY_START+OPENSBI_SIZE;

    // mapping kernel text X|-|R|V
  	//text段映射，权限为0b1011=11
    size=(uint64)(&_srodata)-(uint64)(&_stext);
    create_mapping(swapper_pg_dir, va, pa, size, 11);
    va += size;
    pa += size;

    // mapping kernel rodata -|-|R|V
  	//data段映射，权限为0b0011=3
    size=(uint64)(&_sdata)-(uint64)(&_srodata);
    create_mapping(swapper_pg_dir, va, pa, size, 3);
    va += size;
    pa += size;

    // mapping other memory -|W|R|V
  	//其余映射，权限为0b0111=7
    size=PHY_SIZE-((uint64)(&_sdata)-(uint64)(&_stext));
    create_mapping(swapper_pg_dir, va, pa, size, 7);
    va += size;
    pa += size;
   
    // set satp with swapper_pg_dir
  	// 方法与head.S中的函数relocate一致，将swapper_pg_dir载入satp
    uint64 addr = (uint64)swapper_pg_dir - PA2VA_OFFSET;
    asm volatile (
        "mv t0, %0\n"
        "srli t0, t0, 12\n"
        "li t1, 1\n"
        "slli t1, t1, 63\n"
        "or t0, t0, t1\n"
        "csrw satp, t0"
        :
        :"r"(addr)
        :"memory"
    );

    // flush TLB
    asm volatile("sfence.vma zero, zero");
  
    // flush icache
    asm volatile("fence.i");
    printk("...setup_vm_final done!\n");
    return;
}

